# Example file showing a circle moving on screen
import pygame
import random
import math
import find

# 游戏状态 0 未开始  1 进行中   2 游戏死亡
gameStatus = 1

# 游戏速度
speed = 60

# 积分
scores = 0

# 是否有食物
foodStatus = False
foodPoint = {'x':0, 'y':0}

# x,y
x,y = 40,30
vx,vy = 25,25

# pygame setup
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((x * vx, y * vy))
pygame.display.set_caption("贪吃蛇")
clock = pygame.time.Clock()
running = True
dt = 0

# 常量方向
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'

# 音效
pygame.mixer.music.load("bg.mp3")
pygame.mixer.music.set_volume(0.8)
pygame.mixer.music.play(-1)

food_sound = pygame.mixer.Sound("food.mp3")
game_over_sound = pygame.mixer.Sound("game_over.mp3")

# 引入图片
bg_img = pygame.image.load("./image/bg.png").convert() # 背景图片
bg_img = pygame.transform.scale(bg_img, (x * vx, y * vy))

body_img = pygame.image.load("./image/body.png").convert_alpha() # 身体
up_img = pygame.image.load("./image/up.png").convert_alpha() # 向上
down_img = pygame.image.load("./image/down.png").convert_alpha() # 向下
left_img = pygame.image.load("./image/left.png").convert_alpha() # 向 左
right_img = pygame.image.load("./image/right.png").convert_alpha() # 向 右
food_img = pygame.image.load("./image/food.png").convert_alpha() # 食物

imgKeys = {
    UP: up_img,
    DOWN: down_img,
    LEFT: left_img,
    RIGHT: right_img,
}

# 文字：
font = pygame.font.SysFont('arial', 20)

# 路径
paths = []
pathsFoot = []

# 初始方向
direction = RIGHT
startX = x / 2 - 2
startY = y / 2 - 2
initPoint = [
  {'x': startX,'y': startY},
  {'x': startX - 1,'y': startY},
  {'x': startX - 2,'y': startY},
  {'x': startX - 3,'y': startY},
]
point = []
for i in range(len(initPoint)):
   point.append({
      'x': initPoint[i]['x'],
      'y': initPoint[i]['y'],
   })

def move():
    global point # 声明为全局变量
    global foodStatus
    global scores
    global gameStatus
    global x
    global y
    """
    蛇移动
    """

    # [ (13,8), (12,8) , (11,8)]

    lastPoint = []
    # 渲染蛇身 解决方案 使用新的数组 将上一次值保存下来
      # 正序 会导致 后面的点永远都是身体的第一个点 
      # 倒序会导致 前面的点永远都是最后一点 
    for i in range(len(point) - 1):
        lastPoint.append({
            'x': point[i]['x'],
            'y': point[i]['y'],
        })
    
    # 移动方向
    if direction == UP:
      point[0]['y'] -= 1
    elif direction == DOWN:
      point[0]['y'] += 1
    elif direction == RIGHT:
      point[0]['x'] += 1
    elif direction == LEFT:
      point[0]['x'] -= 1

    # 绘制蛇头
    screen.blit(imgKeys[direction], (point[0]['x'] * vx, point[0]['y'] * vy))

    # 绘制蛇身
    errorFlag = False
    point = [point[0]]
    for i in range(len(lastPoint)):
        if lastPoint[i]['x'] == point[0]['x'] and lastPoint[i]['y'] == point[0]['y']:
           errorFlag = True
           break
        screen.blit(body_img, (lastPoint[i]['x'] * vx, lastPoint[i]['y'] * vy))
        point.append({
            'x': lastPoint[i]['x'],
            'y': lastPoint[i]['y'],
        })
    
    # 判断是否吃到食物
    if point[0]['x'] == foodPoint['x'] and point[0]['y'] == foodPoint['y']:
       l = len(point)
       # 需扩展 蛇的方向来决定添加的点 此处是为了演示效果 不考虑细节 如果帧数高 可以忽略不计
       point.append({
            'x': point[l-1]['x'],
            'y': point[l-1]['y'],
        })
       food_sound.play()
       foodStatus = False
       scores += 1
    # 判断蛇头是否过界
    elif point[0]['x'] <= 0 or point[0]['x'] >= x or point[0]['y'] <= 0 or point[0]['y'] >= y or errorFlag:
       gameStatus = 2
       game_over_sound.play()
    # 判断 蛇头 是否撞到蛇身
    return None

def food():
    global foodStatus
    global point
    """
    生成食物 防止生成在蛇身上
    """
    if foodStatus == False:
      for i in range(10): # 防止生成在蛇身上 迭代10次 如果不是就跳出
        foodPoint['x'] = random.randint(1,x-1)
        foodPoint['y'] = random.randint(1,y-1)
        if foodPoint not in point:
            setPath()
            break
      foodStatus = True
    screen.blit(food_img, (foodPoint['x'] * vx, foodPoint['y'] * vy))
    return None

def initGame():
    global scores
    global point
    global initPoint
    global direction
    """
    初始化游戏
    :return 
    """
    scores = 0

    point = []
    for i in range(len(initPoint)):
      point.append({
        'x': initPoint[i]['x'],
        'y': initPoint[i]['y'],
      })
      direction = RIGHT
    return None

def autoMove():
    global point
    global foodPoint
    global direction
    global LEFT
    global RIGHT
    global UP
    global DOWN
    """
      自动移动
      :return
    """
    sX = point[0]['x']
    sY = point[0]['y']

    eX = foodPoint['x']
    eY = foodPoint['y']

    if len(point) > 10 and len(pathsFoot) == 0:
        setPathFoot()
        [eX,eY] = pathsFoot.pop()
        print("移动后的：move 尾巴", eX,eY)

    # 先上下 后左右 但长度上来还是会撞身子 
      # 解决方案是：把当前方向的头部算出来 判断是否撞身子 
      # 策略1：如果撞身子 就另取方向 一共就四个方向 只要确保其中一个方向不会撞身子就行
    if sY != eY:
      # 有限制 上边 不能往下  同理 下边不能往上
      move  = UP  if sY > eY else DOWN
      if move == UP and direction != DOWN:
          direction = move
      elif move == DOWN and direction != UP:
          direction = move
      else: # 如果上下都不能移动 就移动左右
        direction = LEFT if sX > eX else RIGHT
    elif sX != eX:
      # 有限制 左边 不能往右  同理 右边不能往左
      move  = LEFT  if sX > eX else RIGHT
      if move == LEFT and direction != RIGHT:
          direction = move
      elif move == RIGHT and direction != LEFT:
          direction = move
      else: # 如果左右都不能移动 就移动上下
        direction = UP if sY > eY else DOWN

    # 记录方向映射 用于做判断是否验证了
    directionKeys = {
      "up":UP,
      "down":DOWN,
      "left":LEFT,
      "right":RIGHT,
    }

    directions = []
    directions.append(directionKeys[direction])
    del directionKeys[direction]

    def check(x,y,key):
      """
      检查是否撞身子
      :param x: x坐标
      :param y: y坐标
      :param key: 方向
      """
      global point
      global UP
      global DOWN
      global LEFT
      global RIGHT
      if key == UP:
        y -= 1
      elif key == DOWN:
        y += 1
      elif key == RIGHT:
        x += 1
      elif key == LEFT:
        x -= 1
      # 位置没有找到直接跳出
      return {'x':x,'y':y} in point

    checkFlag = False
    for i in range(len(directions)):
        key = directions[i]
        checkFlag = check(sX,sY,key)
        if checkFlag:
          break

    # 找到了就 遍历剩下的方向 
      # 此处还需要优化算法 优化方向  因为长度很大可能会出现一个圈子 往里面走 就死胡同了 所以要把圈子里面的数组全部排除掉
      # 方案： 找到 蛇所有位置的 最小x1,y1 最大x2,y2 绝对值：|x1-x2|*|y1-y2| 面积 如果等于面积等于0 说明在同一行 或 同一列里面 不需要做处理
        # 判断蛇长度 如果蛇长度 >= 这个面积 然后排除掉这个范围内的点 如果不是就不排除
    if checkFlag:
        # print('撞身子了，重新遍历剩下的方向',direction, directionKeys)
        minX,minY,maxX,maxY = 999,999,0,0
        for i in range(len(point)):
          maxX = point[i]['x'] if point[i]['x'] > maxX else maxX
          maxY = point[i]['y'] if point[i]['y'] > maxY else maxY
          minX = point[i]['x'] if point[i]['x'] < minX else minX
          minY = point[i]['y'] if point[i]['y'] < minY else minY
        area = math.fabs((maxX - minX ) * (maxY - minY))
        lastDirection = direction
        # print("面积为：",area, len(point), point)
        for key,value in directionKeys.items():
           checkFlag = check(sX,sY,value)
           if not checkFlag:
             direction = key
             break
        # print('重新遍历后的方向',direction)
        if lastDirection == direction:
           print('死胡同了',direction)
    return None

def setPath():
    global point
    global foodPoint
    global x
    global y
    global paths
    """
      设置路径点 在生成食物的时候调用
    """
    body = []
    for i in range(len(point)):
      body.append([point[i]['x'],point[i]['y']])
    paths = find.findPath(x,y, [int(point[0]['x']),int(point[0]['y'])], [int(foodPoint['x']), int(foodPoint['y'])], body)
    print('paths', paths)
    return None

def setPathFoot():
    global point
    global x
    global y
    global pathsFoot
    """
      设置路径点 在生成食物的时候调用
    """
    body = []
    for i in range(len(point)):
      body.append([point[i]['x'],point[i]['y']])
    foot = point[len(point) - 1]
    pathsFoot = find.findPath(x,y, [int(point[0]['x']),int(point[0]['y'])], [int(foot['x']), int(foot['y'])], body)
    print('pathsFoot', pathsFoot)
    return None

def autoMove2():
    global paths
    global direction
    global LEFT
    global RIGHT
    global UP
    global DOWN
    """
     寻找到所有满足的路径
    """
    # 没有目标点 只能听天由命了
    if paths == False or len(paths) == 0:
      return autoMove()
    [moveX,moveY] = paths.pop()
    if point[0]['y'] != moveY:
      direction = DOWN if point[0]['y'] < moveY else UP
    elif point[0]['x'] != moveX:
      direction = RIGHT if point[0]['x'] < moveX else LEFT
    setPath()
    print('paths', paths)
    print('移动后的：move', moveX,moveY)
    print('当前点：point', point[0])
    print('direction', direction)
    return None
while running:
    # poll for events
    # pygame.QUIT event means the user clicked X to close your window
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # fill the screen with a color to wipe away anything from last frame
    # screen.fill((224,193,193))

    # bg
    screen.blit(bg_img,(0,0))

    # 监听键盘按下 且 不能反方向移动
    keys = pygame.key.get_pressed()

    # 游戏状态
    if gameStatus == 0:
      text = font.render("Press the spacebar to start the game", True,(255,0,0))
      TextRect = text.get_rect()
      TextRect.center = ((x*vx /2),(y*vy/2))
      screen.blit(text, TextRect)
      if keys[pygame.K_SPACE]:
          gameStatus = 1
          initGame()
    elif gameStatus == 1:
      text = font.render("Scores:" + str(scores), True,(255,255,255))
      screen.blit(text,(0,0))
      
      autoMove()
      # print('自动调整方向为：',direction)

      if keys[pygame.K_UP] and direction != DOWN:
          direction = UP
      if keys[pygame.K_DOWN] and direction != UP:
          direction = DOWN
      if keys[pygame.K_LEFT]  and direction != RIGHT:
          direction = LEFT
      if keys[pygame.K_RIGHT] and direction != LEFT:
          direction = RIGHT

      # 食物
      food()

      # 移动
      move()

      # 自动移动
      # autoMove2()

    else:
      text = font.render("Game over! Scores:" + str(scores), True,(255,0,0))
      TextRect = text.get_rect()
      TextRect.center = ((x*vx /2),(y*vy/2))
      screen.blit(text, TextRect)
      if keys[pygame.K_SPACE]:
          gameStatus = 1
          initGame()
   

    # flip() the display to put your work on screen
    pygame.display.flip()

    # limits FPS to 60
    # dt is delta time in seconds since last frame, used for framerate-
    # independent physics.
    # dt = clock.tick(60) / 1000
    clock.tick(speed)

pygame.quit()